/*! ******************************************************************************
 *
 * Pentaho
 *
 * Copyright (C) 2024 by Hitachi Vantara, LLC : http://www.pentaho.com
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file.
 *
 * Change Date: 2029-07-20
 ******************************************************************************/


package org.pentaho.platform.web.http;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IContentOutputHandler;
import org.pentaho.platform.api.engine.IMimeTypeListener;
import org.pentaho.platform.api.engine.IOutputHandler;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.repository.IContentItem;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.web.http.messages.Messages;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;

public class HttpOutputHandler implements IOutputHandler, IMimeTypeListener {
  private static final Log logger = LogFactory.getLog( HttpOutputHandler.class );

  private HttpServletResponse response;

  protected IContentItem outputContent;

  private IContentItem feedbackContent;

  boolean allowFeedback;

  @Deprecated
  private boolean contentGenerated;

  private IPentahoSession session;

  private IMimeTypeListener mimeTypeListener;

  private int outputType = IOutputHandler.OUTPUT_TYPE_DEFAULT;

  private OutputStream destinationOutputStream;

  private boolean responseExpected;

  public HttpOutputHandler( final HttpServletResponse response, final OutputStream outputStream,
      final boolean allowFeedback ) {
    this.response = response;
    destinationOutputStream = outputStream;

    outputContent = new HttpContentItem( destinationOutputStream, this );
    ( (HttpContentItem) outputContent ).setMimeTypeListener( this );
    feedbackContent = new HttpContentItem( destinationOutputStream, this );

    this.allowFeedback = allowFeedback;
    contentGenerated = false;
  }

  public void setSession( final IPentahoSession session ) {
    this.session = session;
  }

  public IPentahoSession getSession() {
    return session;
  }

  public void setOutputPreference( final int outputType ) {
    this.outputType = outputType;
  }

  public int getOutputPreference() {
    return outputType;
  }

  public void setMimeType( final String mimeType ) {

    if ( mimeTypeListener != null ) {
      mimeTypeListener.setMimeType( mimeType );
    }
    response.setContentType( mimeType );
  }

  public void setName( String name ) {
    if ( mimeTypeListener != null ) {
      mimeTypeListener.setName( name );
    }
  }

  @Deprecated
  public boolean contentDone() {
    return contentGenerated;
  }

  public boolean allowFeedback() {
    return allowFeedback;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.pentaho.core.solution.IOutputHandler#getFeedbackStream()
   */
  public IContentItem getFeedbackContentItem() {
    if ( allowFeedback ) {
      // assume that content is generated becuase of this
      contentGenerated = true;
      /*
       * if someone is requesting a feedbackContentItem, we can assume they tend to write feedback back to the client,
       * so we set the flag here
       */
      responseExpected = true;
      return feedbackContent;
    }
    return null;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.pentaho.core.solution.IOutputHandler#getContentStream()
   */
  public IContentItem getOutputContentItem( final String objectName, final String contentName, final String instanceId,
      final String mimeType ) {
    if ( objectName.equals( IOutputHandler.RESPONSE ) && contentName.equals( IOutputHandler.CONTENT ) ) {
      // assume that content is generated becuase of this
      // change the content type if necessary
      if ( mimeType != null ) {
        outputContent.setMimeType( mimeType );
      }
      contentGenerated = true;
      /*
       * if someone is requesting an outputContentItem, they are intending to write to the response output stream.
       */
      responseExpected = true;

      return outputContent;
    } else {
      IContentOutputHandler output = null;

      // this code allows us to stay backwards compatible
      if ( ( contentName != null ) && ( contentName.indexOf( ":" ) == -1 ) ) { //$NON-NLS-1$
        output = PentahoSystem.getOutputDestinationFromContentRef( objectName + ":" + contentName, session ); //$NON-NLS-1$
      } else {
        output = PentahoSystem.getOutputDestinationFromContentRef( contentName, session );
        if ( output == null ) {
          output = PentahoSystem.getOutputDestinationFromContentRef( objectName + ":" + contentName, session ); //$NON-NLS-1$
        }
      }
      if ( output != null ) {
        output.setInstanceId( instanceId );

        String filePath = "~/workspace/" + contentName; //$NON-NLS-1$
        if ( contentName.startsWith( "/" ) ) {
          filePath = contentName;
        }
        output.setSolutionPath( filePath );
        output.setMimeType( mimeType );
        output.setSession( session );
        return output.getFileOutputContentItem();
      }
    }
    return null;
  }

  public void setOutput( final String name, final Object value ) throws IOException {
    if ( value == null ) {
      HttpOutputHandler.logger.warn( Messages.getInstance().getString( "HttpOutputHandler.WARN_0001_VALUE_IS_NULL" ) ); //$NON-NLS-1$
      return;
    }

    /*
     * if setOutput is called, it means someone intends to write some data to the response stream, which they may not
     * have gotten a handle to earlier through getOutputContentItem*. So we need to set the flag here.
     */
    responseExpected = true;

    if ( "redirect".equalsIgnoreCase( name ) ) { //$NON-NLS-1$
      try {
        response.sendRedirect( value.toString() );
      } catch ( IOException ioe ) {
        HttpOutputHandler.logger.error( Messages.getInstance().getString(
          "HttpOutputHandler.ERROR_0001_REDIRECT_FAILED", value.toString() ), ioe ); //$NON-NLS-1$
      }
    } else if ( "header".equalsIgnoreCase( name ) ) { //$NON-NLS-1$
      try {
        if ( value instanceof Map ) {
          for ( Iterator it = ( (Map) value ).entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = (Map.Entry) it.next();
            response.addHeader( entry.getKey().toString(), entry.getValue().toString() );
          }
        } else {
          String strVal = value.toString();
          int i = strVal.indexOf( '=' );
          String headerName = strVal.substring( 0, i );
          String headerValue = strVal.substring( i + 1 );
          response.addHeader( headerName, headerValue );
        }
      } catch ( IndexOutOfBoundsException e ) {
        HttpOutputHandler.logger.error( e.getLocalizedMessage() );
      }
    } else if ( IOutputHandler.CONTENT.equalsIgnoreCase( name ) ) {
      if ( value instanceof IContentItem ) {
        IContentItem content = (IContentItem) value;
        // See if we should process the input stream. If it's from
        // the content repository, then there's an input stream.
        // SimpleContentItem and HttpContentItem both return null from
        // getInputStream().
        InputStream inStr = content.getInputStream();
        if ( inStr != null ) {
          if ( ( response.getContentType() == null )
              || ( !response.getContentType().equalsIgnoreCase( content.getMimeType() ) ) ) {
            // response.setContentType( content.getMimeType() );
            setMimeType( content.getMimeType() );
          }
          try {
            // Bad idea to write to the output stream directly. This prevents us from
            // auditing writes. Use the managed destinationOutputStream instead.
            // OutputStream outStr = response.getOutputStream();
            int inCnt = 0;
            byte[] buf = new byte[4096];
            while ( -1 != ( inCnt = inStr.read( buf ) ) ) {
              destinationOutputStream.write( buf, 0, inCnt );
            }
          } finally {
            try {
              inStr.close();
            } catch ( IOException ignored ) {
              //ignored
            }
          }
          contentGenerated = true;
        }
      } else {
        if ( response.getContentType() == null ) {
          setMimeType( "text/html" ); //$NON-NLS-1$
        }

        destinationOutputStream.write( value.toString().getBytes() );
        contentGenerated = true;
      }
    }
  }

  public IMimeTypeListener getMimeTypeListener() {
    return mimeTypeListener;
  }

  public void setMimeTypeListener( final IMimeTypeListener mimeTypeListener ) {
    this.mimeTypeListener = mimeTypeListener;
  }

  public HttpServletResponse getResponse() {
    return response;
  }

  public void setResponse( final HttpServletResponse response ) {
    this.response = response;
  }

  public boolean isResponseExpected() {
    return responseExpected;
  }

  public boolean isWritable() {
    return false;
  }
}
